﻿using SmartPlant.Updater.Properties;
using SmartPlant.WinCore.Helper;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Handlers;
using System.Net.Mime;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SmartPlant.Updater
{
    internal partial class DownloadUpdateDialog : Form
    {
        private readonly UpdateInfoEventArgs _args;

        private string _tempFile;

        private HttpClient _webClient;

        private DateTime _startedAt;

        public DownloadUpdateDialog(UpdateInfoEventArgs args)
        {
            InitializeComponent();

            _args = args;

            if (AutoUpdater.Mandatory && AutoUpdater.UpdateMode == Mode.ForcedDownload)
            {
                ControlBox = false;
            }
        }

        private async void DownloadUpdateDialogLoad(object sender, EventArgs e)
        {
            var uri = new Uri(_args.DownloadURL);

            var handler = new HttpClientHandler();
            var processMessageHander = new ProgressMessageHandler(handler);
            if (string.IsNullOrEmpty(AutoUpdater.DownloadPath))
            {
                _tempFile = Path.GetTempFileName();
            }
            else
            {
                _tempFile = Path.Combine(AutoUpdater.DownloadPath, $"{Guid.NewGuid()}.tmp");
                if (!Directory.Exists(AutoUpdater.DownloadPath))
                {
                    Directory.CreateDirectory(AutoUpdater.DownloadPath);
                }
            }

            processMessageHander.HttpReceiveProgress += ProcessMessageHander_HttpReceiveProgress;
            try
            {
                _webClient = new HttpClient(processMessageHander);
                var response = await _webClient.GetAsync(uri);
                if (response.StatusCode != HttpStatusCode.OK)
                    throw new HttpRequestException();
                using var stream = await response.Content.ReadAsStreamAsync();
                if (stream != null)
                {
                    await WriteFileContentAsync(stream, _tempFile);
                    WebClientOnDownloadFileCompleted($"update_{Guid.NewGuid()}.exe");
                }
            }
            catch (Exception x)
            {
                MessageBox.Show(x.Message, x.GetType().ToString(), MessageBoxButtons.OK, MessageBoxIcon.Error);
                _webClient.Dispose();
                _webClient = null;
                DialogResult = _webClient == null ? DialogResult.Cancel : DialogResult.OK;
                FormClosing -= DownloadUpdateDialog_FormClosing;
                Close();
            }
        }

        private void ProcessMessageHander_HttpReceiveProgress(object sender, HttpProgressEventArgs e)
        {
            if (_startedAt == default(DateTime))
            {
                _startedAt = DateTime.Now;
            }
            else
            {
                var timeSpan = DateTime.Now - _startedAt;
                long totalSeconds = (long)timeSpan.TotalSeconds;
                if (totalSeconds > 0)
                {
                    var bytesPerSecond = e.BytesTransferred / totalSeconds;
                    var msg = string.Format(Resources.DownloadSpeedMessage, BytesToString(bytesPerSecond));
                    labelInformation.Invoke(() => { labelInformation.Text = msg; });
                }
            }

            long totalBytes = 0L;
            if (e.TotalBytes != null)
                totalBytes = e.TotalBytes.Value;
            else
                totalBytes = (1024 * 1024) * 100;

            var txt = $@"{BytesToString(e.BytesTransferred)} / {BytesToString(totalBytes)}";
            labelSize.Invoke(() => { labelSize.Text = txt; });
            progressBar.Invoke(() => { progressBar.Value = e.ProgressPercentage; });
        }

        private void OnDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            if (_startedAt == default(DateTime))
            {
                _startedAt = DateTime.Now;
            }
            else
            {
                var timeSpan = DateTime.Now - _startedAt;
                long totalSeconds = (long)timeSpan.TotalSeconds;
                if (totalSeconds > 0)
                {
                    var bytesPerSecond = e.BytesReceived / totalSeconds;
                    labelInformation.Text =
                        string.Format(Resources.DownloadSpeedMessage, BytesToString(bytesPerSecond));
                }
            }

            labelSize.Text = $@"{BytesToString(e.BytesReceived)} / {BytesToString(e.TotalBytesToReceive)}";
            progressBar.Value = e.ProgressPercentage;
        }

        private void WebClientOnDownloadFileCompleted(string fileName)
        {
            if (_args.CheckSum != null)
            {
                CompareChecksum(_tempFile, _args.CheckSum);
            }

            ContentDisposition contentDisposition = null;

            //var fileName = string.IsNullOrEmpty(contentDisposition?.FileName)
            //    ? Path.GetFileName(_webClient.BaseAddress.LocalPath)
            //    : contentDisposition.FileName;

            var tempPath =
                Path.Combine(
                    string.IsNullOrEmpty(AutoUpdater.DownloadPath) ? Path.GetTempPath() : AutoUpdater.DownloadPath,
                    fileName);

            if (File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }

            File.Move(_tempFile, tempPath);

            string installerArgs = null;
            if (!string.IsNullOrEmpty(_args.InstallerArgs))
            {
                installerArgs = _args.InstallerArgs.Replace("%path%",
                    Path.GetDirectoryName(Process.GetCurrentProcess().MainModule?.FileName));
            }

            var processStartInfo = new ProcessStartInfo
            {
                FileName = tempPath,
                UseShellExecute = true,
                Arguments = installerArgs ?? string.Empty
            };

            var extension = Path.GetExtension(tempPath);
            if (extension.Equals(".zip", StringComparison.OrdinalIgnoreCase))
            {
                string installerPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "win-x86\\ZipExtractor.exe");

                FileInfo file = new FileInfo(installerPath);
                if (!file.Directory.Exists)
                {
                    file.Directory.Create();
                }

                File.WriteAllBytes(installerPath, Resources.ZipExtractor);

                string executablePath = Process.GetCurrentProcess().MainModule?.FileName;
                string extractionPath = Path.GetDirectoryName(executablePath);

                if (!string.IsNullOrEmpty(AutoUpdater.InstallationPath) &&
                    Directory.Exists(AutoUpdater.InstallationPath))
                {
                    extractionPath = AutoUpdater.InstallationPath;
                }

                var arguments = new StringBuilder($"--input \"{tempPath}\" --output \"{extractionPath}\" --executable \"{executablePath}\"");

                if (AutoUpdater.ClearAppDirectory)
                {
                    arguments.Append(" --clear");
                }

                string[] args = Environment.GetCommandLineArgs();
                for (int i = 1; i < args.Length; i++)
                {
                    if (i.Equals(1))
                    {
                        arguments.Append(" --args \"");
                    }

                    arguments.Append($"\"{args[i]}\"");
                    arguments.Append(i.Equals(args.Length - 1) ? "\"" : " ");
                }

                processStartInfo = new ProcessStartInfo
                {
                    FileName = installerPath,
                    UseShellExecute = true,
                    Arguments = arguments.ToString()
                };
            }
            else if (extension.Equals(".msi", StringComparison.OrdinalIgnoreCase))
            {
                processStartInfo = new ProcessStartInfo
                {
                    FileName = "msiexec",
                    Arguments = $"/i \"{tempPath}\"",
                };
                if (!string.IsNullOrEmpty(installerArgs))
                {
                    processStartInfo.Arguments += " " + installerArgs;
                }
            }
            else if (extension.Equals(".exe", StringComparison.OrdinalIgnoreCase))
            {
                processStartInfo = new ProcessStartInfo(tempPath);
                if (!string.IsNullOrEmpty(installerArgs))
                {
                    processStartInfo.Arguments += " " + installerArgs;
                }
            }
            if (AutoUpdater.RunUpdateAsAdmin)
            {
                processStartInfo.Verb = "runas";
            }

            try
            {
                Process.Start(processStartInfo);
            }
            catch (Win32Exception e)
            {
                _webClient?.Dispose();
                if (e.NativeErrorCode == 1223)
                {
                    _webClient = null;
                }
                else
                {
                    MessageBox.Show(e.Message, e.GetType().ToString(), MessageBoxButtons.OK, MessageBoxIcon.Error);
                    _webClient = null;
                }
            }
            finally
            {
                _webClient?.Dispose();
                DialogResult = _webClient == null ? DialogResult.Cancel : DialogResult.OK;
                FormClosing -= DownloadUpdateDialog_FormClosing;
                Close();
            }
        }

        private static string BytesToString(long byteCount)
        {
            string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" };
            if (byteCount == 0)
                return "0" + suf[0];
            long bytes = Math.Abs(byteCount);
            int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
            double num = Math.Round(bytes / Math.Pow(1024, place), 1);
            return $"{(Math.Sign(byteCount) * num).ToString(CultureInfo.InvariantCulture)} {suf[place]}";
        }

        private static void CompareChecksum(string fileName, CheckSum checksum)
        {
            using var hashAlgorithm = HashAlgorithm.Create(string.IsNullOrEmpty(checksum.HashingAlgorithm) ? "MD5" : checksum.HashingAlgorithm);
            using var stream = File.OpenRead(fileName);
            if (hashAlgorithm != null)
            {
                var hash = hashAlgorithm.ComputeHash(stream);
                var fileChecksum = BitConverter.ToString(hash).Replace("-", string.Empty).ToLowerInvariant();

                if (fileChecksum == checksum.Value.ToLower()) return;

                throw new Exception(Resources.FileIntegrityCheckFailedMessage);
            }

            throw new Exception(Resources.HashAlgorithmNotSupportedMessage);
        }

        private void DownloadUpdateDialog_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (AutoUpdater.Mandatory && AutoUpdater.UpdateMode == Mode.ForcedDownload)
            {
                AutoUpdater.Exit();
                return;
            }
        }

        /// <summary>
        /// 将远程文件流下载至本地下载文件
        /// </summary>
        /// <param name="srcStream"></param>
        /// <param name="targetFilePath">物理地址</param>
        private async Task<bool> WriteFileContentAsync(Stream srcStream, string targetFilePath)
        {
            try
            {
                var fileInfo = new FileInfo(targetFilePath);
                if (!fileInfo.Directory.Exists)
                    fileInfo.Directory.Create();
                //srcStream.Position = 0;
                using Stream fsWriteStream = File.OpenWrite(targetFilePath);
                //构建写文件流对象
                using var fsWrite = new BinaryWriter(fsWriteStream); //没有找到就创建
                                                                     //开辟临时缓存内存
                byte[] byteArrayRead = new byte[1024 * 1024]; // 1字节*1024 = 1k 1k*1024 = 1M内存

                //通过死缓存去读文本中的内容
                while (true)
                {
                    //readCount 这个是保存真正读取到的字节数
                    int readCount = await srcStream.ReadAsync(byteArrayRead, 0, byteArrayRead.Length);

                    //开始写入读取到缓存内存中的数据到目标文本文件中
                    fsWrite.Write(byteArrayRead, 0, readCount);


                    //既然是死循环 那么什么时候我们停止读取文本内容 我们知道文本最后一行的大小肯定是小于缓存内存大小的
                    if (readCount < byteArrayRead.Length)
                    {
                        break; //结束循环
                    }
                }
                return true;
            }
            catch (Exception x)
            {
                LoggerHelper.Current.Error(x.Message, x);
                return false;
            }
        }
    }
}
